package com.product.statemachine.common.spring.bootstrap;

import com.product.statemachine.common.DomainStateMachineEnvironment;
import com.product.statemachine.common.DomainStateMachineFactory;
import com.product.statemachine.common.annotation.StateMachineInOperationAnnotation;
import com.product.statemachine.common.event.StateEvent;
import com.product.statemachine.common.exception.StateMachineException;
import com.product.statemachine.common.executor.DomainStateEventExecutor;
import com.product.statemachine.common.spring.config.StateConfigProperties;
import com.product.statemachine.common.spring.config.StateMachineConfigProperties;
import com.product.statemachine.common.spring.config.StateMachineFlowConfigProperties;
import com.product.statemachine.common.spring.config.StateMachineProperties;
import com.product.statemachine.common.state.State;
import com.product.statemachine.common.util.ClassUtils;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
  * @author: caoxingming
  * @data: 2022/6/9 12:00 下午
  * @description: 事件状态机bootstrap配置类
  **/
@Slf4j
@Component
public class StateMachineBootstrapConfiguration  implements SmartInitializingSingleton, ApplicationContextAware {

    @Setter
    private ApplicationContext applicationContext;

    @SneakyThrows
    @Override
    public void afterSingletonsInstantiated() {
        log.info("creating statemachine Bootstrap Beans");
        createStateMachineBootstrapBeans();
        log.info("statemachine Bootstrap Beans created.");
    }

    /**
     * 创建状态机bootstrap instances
     */
    public void createStateMachineBootstrapBeans() throws Exception {
        StateMachineProperties stateMachineProperties = applicationContext.getBean(StateMachineProperties.class);
        DomainStateMachineEnvironment domainStateMachineEnv = applicationContext.getBean(DomainStateMachineEnvironment.class);
        DomainStateMachineFactory domainStateMachineFactory = applicationContext.getBean(DomainStateMachineFactory.class);
        initStateMachineInOperate(domainStateMachineEnv);
        initStateMachineInOperateStates(stateMachineProperties,domainStateMachineEnv);
        initStateMachineInOperateStateEvents(stateMachineProperties,domainStateMachineEnv);
        initStateMachineInOperateStateEventExecutors(domainStateMachineEnv);
        domainStateMachineEnv.checkDomainStateMachineEnvironment();
        domainStateMachineFactory.initFactory();
    }



    /**
     * 初始化状态机全局事件执行器
     * @param domainStateMachineEnvironment
     */
    private void initStateMachineInOperateStateEventExecutors(DomainStateMachineEnvironment domainStateMachineEnvironment) {
        if(!domainStateMachineEnvironment.isInOperateStateMachineExists()) {
            return;
        }
        Collection<DomainStateEventExecutor> executors = applicationContext.getBeansOfType(DomainStateEventExecutor.class).values();
        //代码已配置的状态机执行器
        HashMap<String, DomainStateEventExecutor> domainStateEventExecutorExistsMap = getDomainStateEventExecutor(executors);

        HashMap<String, HashMap<String, DomainStateEventExecutor>> domainStateEventExecutors = domainStateMachineEnvironment.getDomainStateEventExecutors();
        HashMap<String, HashMap<String, StateEvent>> stateEventHashMap = domainStateMachineEnvironment.getStateEventHashMap();
        Set<Map.Entry<String, HashMap<String, StateEvent>>> stateEventEntrySet = stateEventHashMap.entrySet();
        for (Map.Entry<String, HashMap<String, StateEvent>> entry : stateEventEntrySet) {
            HashMap<String, DomainStateEventExecutor> domainStateEventExecutorHashMap = new HashMap<>();
            String stateMachineName = entry.getKey();
            HashMap<String, StateEvent> eventHashMap = entry.getValue();
            Set<Map.Entry<String, StateEvent>> eventSet = eventHashMap.entrySet();
            for(Map.Entry<String, StateEvent> eventEntry : eventSet) {
                StateEvent stateEvent = eventEntry.getValue();
                DomainStateEventExecutor domainStateEventExecutor = domainStateEventExecutorExistsMap.get(ClassUtils.getClassPackageName(stateEvent.getClass()));
                domainStateEventExecutorHashMap.put(ClassUtils.getClassPackageName(stateEvent.getClass()),domainStateEventExecutor);
            }
            domainStateEventExecutors.put(stateMachineName,domainStateEventExecutorHashMap);
        }
    }


    /**
     *
     * @return
     */
    private HashMap<String,DomainStateEventExecutor> getDomainStateEventExecutor(Collection<DomainStateEventExecutor> executors) {
        HashMap<String,DomainStateEventExecutor> executorHashMap = new HashMap<>(50);
        for(DomainStateEventExecutor executor : executors) {
            Type genericInterface = executor.getClass().getGenericInterfaces()[0];
            Type type = ((ParameterizedType) genericInterface).getActualTypeArguments()[1];
            Class clazz = (Class)type;
            String classPackageName = ClassUtils.getClassPackageName(clazz);
            if(Objects.nonNull(executorHashMap.get(classPackageName))) {
                throw new StateMachineException("存在重复 stateEvent 的 Executor!");
            }
            executorHashMap.put(classPackageName,executor);
        }
        return executorHashMap;
    }

    /**
     * 初始化状态机全局事件
     * @param stateMachineProperties
     * @param domainStateMachineEnvironment
     */
    private void initStateMachineInOperateStateEvents(StateMachineProperties stateMachineProperties, DomainStateMachineEnvironment domainStateMachineEnvironment) throws InstantiationException, IllegalAccessException {
        if(!domainStateMachineEnvironment.isInOperateStateMachineExists()) {
            return;
        }
        HashMap<String, HashMap<String, StateEvent>> stateEventHashMap = domainStateMachineEnvironment.getStateEventHashMap();
        List<String> domainStateMachineNamesInOperate = domainStateMachineEnvironment.getDomainStateMachineNamesInOperate();
        for(String stateMachineName : domainStateMachineNamesInOperate) {
            StateMachineConfigProperties stateMachineConfigProperties = stateMachineProperties.getStatemachine().get(stateMachineName);
            if(Objects.isNull(stateMachineConfigProperties)) {
                continue;
            }
            HashMap<String,State> states = domainStateMachineEnvironment.getStateHashMap().get(stateMachineName);
            Map<String, StateMachineFlowConfigProperties> flows = stateMachineConfigProperties.getFlows();
            if(CollectionUtils.isEmpty(flows)) {
                throw new StateMachineException("statemachine: " + stateMachineName + " . flow cannot be empty!");
            }
            Set<Map.Entry<String, StateMachineFlowConfigProperties>> entries = flows.entrySet();
            HashMap<String, StateEvent> map = new HashMap<>(50);
            for(Map.Entry<String, StateMachineFlowConfigProperties> flow : entries) {
                StateMachineFlowConfigProperties stateMachineFlowConfigProperties = flow.getValue();
                Class<? extends StateEvent> eventClass = stateMachineFlowConfigProperties.getEventClass();
                StateEvent stateEvent = eventClass.newInstance();
                stateEvent.setSourceState(getStateBeanByStateName(stateMachineFlowConfigProperties.getSourceStateName(),states));
                stateEvent.setTargetState(getStateBeanByStateName(stateMachineFlowConfigProperties.getTargetStateName(),states));
                stateEvent.setTargetStateName(stateMachineFlowConfigProperties.getTargetStateName());
                stateEvent.setSourceStateName(stateMachineFlowConfigProperties.getSourceStateName());
                stateEvent.setEventDesc(stateMachineFlowConfigProperties.getEventDesc());
                stateEvent.setEventName(stateMachineFlowConfigProperties.getEventName());
                checkStateEventBean(stateMachineName, stateEvent);
                map.put(stateEvent.getEventName(),stateEvent);
            }
            stateEventHashMap.put(stateMachineName,map);
        }
    }

    /***
     * 检查 stateEvent 初始化 bean
     * @param stateMachineName
     * @param stateEvent
     */
    private void checkStateEventBean(String stateMachineName, StateEvent stateEvent) {
        Optional.ofNullable(stateEvent).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
        Optional.ofNullable(stateEvent.getEventDesc()).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
        Optional.ofNullable(stateEvent.getEventName()).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
        Optional.ofNullable(stateEvent.getSourceStateName()).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
        Optional.ofNullable(stateEvent.getTargetStateName()).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
        Optional.ofNullable(stateEvent.getSourceState()).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
        Optional.ofNullable(stateEvent.getTargetState()).<StateMachineException>orElseThrow(()->{throw new StateMachineException("statemachine: " + stateMachineName + " . stateEvent bean init error!");});
    }


    /**
     * 根据 stateName 获取 State 对象
     * @param sourceStateName
     * @param states
     * @return
     */
    private State getStateBeanByStateName(String sourceStateName, HashMap<String, State> states) {
        return states.get(sourceStateName);
    }


    /**
     * 初始化状态机全局状态
     * @param stateMachineProperties
     * @param domainStateMachineEnvironment
     */
    private void initStateMachineInOperateStates(StateMachineProperties stateMachineProperties, DomainStateMachineEnvironment domainStateMachineEnvironment) {
        if(!domainStateMachineEnvironment.isInOperateStateMachineExists()) {
            return;
        }
        List<String> domainStateMachineNamesInOperate = domainStateMachineEnvironment.getDomainStateMachineNamesInOperate();
        Map<String, StateMachineConfigProperties> statemachine = stateMachineProperties.getStatemachine();
        domainStateMachineNamesInOperate.stream().forEach(stateMachineName -> {
            StateMachineConfigProperties stateMachineConfigProperties = statemachine.get(stateMachineName);
            if(Objects.isNull(stateMachineConfigProperties)){
                return;
            }
            Map<String, StateConfigProperties> stateConfigs = stateMachineConfigProperties.getStates();
            if(CollectionUtils.isEmpty(stateConfigs)) {
                throw new StateMachineException("statemachine: " + stateMachineName + " . states cannot be empty!");
            }
            HashMap<String,State> states = new HashMap<>();
            stateConfigs.values().stream().forEach(stateConfigProperties -> {
                State state = stateConfigPropertiesConvertToState(stateConfigProperties);
                states.put(state.getStateName(),state);
            });
            domainStateMachineEnvironment.getStateHashMap().put(stateMachineName,states);
        });
    }

    /**
     * StateConfigProperties 转化为 State
     * @param stateConfigProperties
     * @return
     */
    private State stateConfigPropertiesConvertToState(StateConfigProperties stateConfigProperties) {
        return State.builder().stateCode(stateConfigProperties.getStateCode())
                .stateName(stateConfigProperties.getStateName())
                .stateDesc(stateConfigProperties.getStateDesc()).build();
    }

    /**
     * 初始化全局配置开启的状态机配置
     * @param domainStateMachineEnvironment
     */
    private void initStateMachineInOperate(DomainStateMachineEnvironment domainStateMachineEnvironment) throws ClassNotFoundException {
        Map<String, Object> stateMachineInOperateMap = applicationContext.getBeansWithAnnotation(StateMachineInOperationAnnotation.class);
        if(CollectionUtils.isEmpty(stateMachineInOperateMap)) {
            return;
        }
        if(stateMachineInOperateMap.size() > 1) {
            throw new StateMachineException("StateMachineInOperationAnnotation can not oversize 1!");
        }
        Object obj = stateMachineInOperateMap.entrySet().iterator().next().getValue();

        StateMachineInOperationAnnotation stateMachineInOperationAnnotation = Class.forName(obj.getClass().getGenericSuperclass().getTypeName()).getAnnotation(StateMachineInOperationAnnotation.class);
        String[] value = stateMachineInOperationAnnotation.value();
        domainStateMachineEnvironment.setDomainStateMachineNamesInOperate(Arrays.asList(value));
    }



    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
